/*
  KeePass Password Safe - The Open-Source Password Manager
  Copyright (C) 2003-2014 Dominik Reichl <dominik.reichl@t-online.de>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

using KeePass.Native;
using KeePass.Util.Spr;

using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Utility;

namespace KeePass.Util
{
	public sealed class AutoTypeEventArgs : EventArgs
	{
		private string m_strSeq; // Never null
		public string Sequence
		{
			get { return m_strSeq; }
			set
			{
				if(value == null) throw new ArgumentNullException("value");
				m_strSeq = value;
			}
		}

		public bool SendObfuscated { get; set; }
		public PwEntryObj Entry { get; private set; }
		

		public AutoTypeEventArgs(string strSequence, bool bObfuscated, PwEntryObj pe)
		{
			if(strSequence == null) throw new ArgumentNullException("strSequence");
			// pe may be null

			m_strSeq = strSequence;
			this.SendObfuscated = bObfuscated;
			this.Entry = pe;
		}
	}

	public static class AutoType
	{
		public static event EventHandler<AutoTypeEventArgs> FilterCompilePre;
		public static event EventHandler<AutoTypeEventArgs> FilterSendPre;
		public static event EventHandler<AutoTypeEventArgs> FilterSend;

		public static event EventHandler<SequenceQueryEventArgs> SequenceQuery;

		public static event EventHandler<SequenceQueriesEventArgs> SequenceQueriesBegin;
		public static event EventHandler<SequenceQueriesEventArgs> SequenceQueriesEnd;

		private static int m_iEventID = 0;
		public static int GetNextEventID()
		{
			return Interlocked.Increment(ref m_iEventID);
		}

		internal static void InitStatic()
		{
			try
			{
				// SendKeys is not used anymore, thus the following is
				// not required:

				// // Enable new SendInput method; see
				// // http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.aspx
				// ConfigurationManager.AppSettings.Set("SendKeys", "SendInput");
			}
			catch(Exception) { Debug.Assert(false); }
		}

		private static bool MatchWindows(string strFilter, string strWindow)
		{
			string strF = strFilter.Trim();

			/* bool bArbStart = strF.StartsWith("*"), bArbEnd = strF.EndsWith("*");

			if(bArbStart) strF = strF.Remove(0, 1);
			if(bArbEnd) strF = strF.Substring(0, strF.Length - 1);

			if(bArbStart && bArbEnd)
				return (strWindow.IndexOf(strF, StrUtil.CaseIgnoreCmp) >= 0);
			else if(bArbStart)
				return strWindow.EndsWith(strF, StrUtil.CaseIgnoreCmp);
			else if(bArbEnd)
				return strWindow.StartsWith(strF, StrUtil.CaseIgnoreCmp);

			return strWindow.Equals(strF, StrUtil.CaseIgnoreCmp); */

			if(strF.StartsWith(@"//") && strF.EndsWith(@"//") && (strF.Length > 4))
			{
				try
				{
					Regex rx = new Regex(strF.Substring(2, strF.Length - 4),
						RegexOptions.IgnoreCase);

					return rx.IsMatch(strWindow);					
				}
				catch(Exception) { }
			}

			return StrUtil.SimplePatternMatch(strF, strWindow, StrUtil.CaseIgnoreCmp);
		}

		private static bool Execute(AutoTypeCtx ctx)
		{
			if(ctx == null) { Debug.Assert(false); return false; }

			string strSeq = ctx.Sequence;
			PwEntryObj pweData = ctx.Entry;

			if(!pweData.GetAutoTypeEnabled()) return false;
			
			AutoTypeEventArgs args = new AutoTypeEventArgs(strSeq,  false ,pweData);

			if(AutoType.FilterCompilePre != null) AutoType.FilterCompilePre(null, args);

		    SprContext sprCtx = new SprContext(pweData, SprCompileFlags.All, true, false);

			args.Sequence = SprEngine.Compile(args.Sequence,sprCtx);

			Application.DoEvents();

			if(AutoType.FilterSendPre != null) AutoType.FilterSendPre(null, args);
			if(AutoType.FilterSend != null) AutoType.FilterSend(null, args);

            Debug.WriteLine("args.Sequence: " + args.Sequence);
            //args.Sequence =pweData.username + "{tab}" + pweData.password   + "{enter}";

			if(args.Sequence.Length > 0)
			{
				try { SendInputEx.SendKeysWait(args.Sequence, args.SendObfuscated); }
				catch(Exception excpAT)
				{
					throw excpAT;
				}
			}

			return true;
		}

		private static bool PerformInternal(AutoTypeCtx ctx, string strWindow)
		{
			if(ctx == null) { Debug.Assert(false); return false; }

			AutoTypeCtx ctxNew = ctx.Clone();

			return AutoType.Execute(ctxNew);
		}

		private static SequenceQueriesEventArgs GetSequencesForWindowBegin(IntPtr hWnd, string strWindow)
		{
			SequenceQueriesEventArgs e = new SequenceQueriesEventArgs(GetNextEventID(), hWnd, strWindow);

			if(AutoType.SequenceQueriesBegin != null)
				AutoType.SequenceQueriesBegin(null, e);

			return e;
		}

		private static void GetSequencesForWindowEnd(SequenceQueriesEventArgs e)
		{
			if(AutoType.SequenceQueriesEnd != null)
				AutoType.SequenceQueriesEnd(null, e);
		}

		// Multiple calls of this method should be wrapped in
		// GetSequencesForWindowBegin and GetSequencesForWindowEnd
        private static List<string> GetSequencesForWindow(PwEntryObj  pwe,
			IntPtr hWnd, string strWindow, int iEventID)
		{
			List<string> l = new List<string>();

			if(pwe == null) { Debug.Assert(false); return l; }
			if(strWindow == null) { Debug.Assert(false); return l; }

			if(!pwe.GetAutoTypeEnabled()) return l;

            SprContext sprCtx = new SprContext(pwe, SprCompileFlags.NonActive);

			// Specifically defined sequences must match before the title,
			// in order to allow selecting the first item as default one
			foreach(AutoTypeAssociation a in pwe.AutoType.Associations)
			{
				string strWndSpec = a.WindowName;
				if(strWndSpec == null) { Debug.Assert(false); continue; }

				strWndSpec = SprEngine.Compile(strWndSpec.Trim(), sprCtx);

				if(MatchWindows(strWndSpec, strWindow))
				{
					string strSeq = a.Sequence;
					if(string.IsNullOrEmpty(strSeq))
						strSeq = pwe.GetAutoTypeSequence();
					AddSequence(l, strSeq);
				}
			}
          
			if(AutoType.SequenceQuery != null)
			{
				SequenceQueryEventArgs e = new SequenceQueryEventArgs(iEventID,	hWnd, strWindow, pwe);
				AutoType.SequenceQuery(null, e);

				foreach(string strSeq in e.Sequences)
					AddSequence(l, strSeq);
			}

			return l;
		}

		private static void AddSequence(List<string> lSeq, string strSeq)
		{
			string strCanSeq = CanonicalizeSeq(strSeq);

			for(int i = 0; i < lSeq.Count; ++i)
			{
				string strCanEx = CanonicalizeSeq(lSeq[i]);
				if(strCanEx.Equals(strCanSeq)) return; // Exists already
			}

			lSeq.Add(strSeq); // Non-canonical version
		}

		private const string StrBraceOpen = @"{1E1F63AB-2F63-4B60-ADBA-7F38B8D7778E}";
		private const string StrBraceClose = @"{34D698D7-CEBF-4AF0-87BF-DC1B1F5E95A0}";
		private static string CanonicalizeSeq(string strSeq)
		{
			// Preprocessing: balance braces
			strSeq = strSeq.Replace(@"{{}", StrBraceOpen);
			strSeq = strSeq.Replace(@"{}}", StrBraceClose);

			StringBuilder sb = new StringBuilder();

			bool bInPlh = false;
			for(int i = 0; i < strSeq.Length; ++i)
			{
				char ch = strSeq[i];

				if(ch == '{') bInPlh = true;
				else if(ch == '}') bInPlh = false;
				else if(bInPlh) ch = char.ToUpper(ch);

				sb.Append(ch);
			}

			strSeq = sb.ToString();

			// Postprocessing: restore braces
			strSeq = strSeq.Replace(StrBraceOpen, @"{{}");
			strSeq = strSeq.Replace(StrBraceClose, @"{}}");

			return strSeq;
		}
        

        public static bool PerformIntoCurrentWindow(PwEntryObj  pe, string strSeq)
		{
	
			IntPtr hWnd;
			string strWindow;
			try
			{
				NativeMethods.GetForegroundWindowInfo(out hWnd, out strWindow, true);
			}
			catch(Exception) { hWnd = IntPtr.Zero; strWindow = null; }

            if (!KeePassLib.Native.NativeLib.IsUnix())
            {
                if (strWindow == null) { Debug.Assert(false); return false; }
            }
            else strWindow = string.Empty;

			Thread.Sleep(100);

			if(strSeq == null)
			{
				SequenceQueriesEventArgs evQueries = GetSequencesForWindowBegin(hWnd, strWindow);

				List<string> lSeq = GetSequencesForWindow(pe, hWnd, strWindow,evQueries.EventID);

				GetSequencesForWindowEnd(evQueries);

				if(lSeq.Count == 0) strSeq = pe.GetAutoTypeSequence();
				else strSeq = lSeq[0];
			}

			AutoTypeCtx ctx = new AutoTypeCtx(strSeq, pe);
			return AutoType.PerformInternal(ctx, strWindow);
		}

		
        /// <summary>
        /// perform data into previous selected window
        /// </summary>
        /// <param name="fCurrent"></param>
        /// <param name="pe"></param>
        /// <param name="strSeq"></param>
        /// <returns></returns>
        public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntryObj  pe, string strSeq)
        {
            if (!pe.GetAutoTypeEnabled()) return false;

            bool bTopMost;
            if (fCurrent != null) bTopMost = fCurrent.TopMost;
            else bTopMost = false;

            if (bTopMost) fCurrent.TopMost = false;

            try
            {
                if (!NativeMethods.LoseFocus(fCurrent)) { Debug.Assert(false); }

                return PerformIntoCurrentWindow(pe, strSeq);
            }
            finally
            {
                if (bTopMost) fCurrent.TopMost = true;
            }
        }

	}
}
